{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">原文转载自「刘悦的技术博客」[https://v3u.cn/a_id_98](https://v3u.cn/a_id_98)\n",
    "\n",
    "用户认证是一个在 web 开发中亘古不变的话题，因为无论是什么系统，什么架构，什么平台，安全性是一个永远也绕不开的问题\n",
    "\n",
    "# Basic认证\n",
    "在 HTTP 中，基本认证（Basic access authentication）是一种用来允许网页浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式。  \n",
    "虽然基本认证非常容易实现，但该方案创建在以下的假设的基础上，即：客户端和服务器主机之间的连接是安全可信的。特别是，如果没有使用 SSL/TLS（https）这样的传输层安全的协议，那么以明文传输的密钥和口令很容易被拦截。该方案也同样没有对服务器返回的信息提供保护。  \n",
    "现存的浏览器保存认证信息直到标签页或浏览器被关闭，或者用户清除历史记录。HTTP 没有为服务器提供一种方法指示客户端丢弃这些被缓存的密钥。这意味着服务器端在用户不关闭浏览器的情况下，并没有一种有效的方法来让用户注销。\n",
    "\n",
    "# OAuth\n",
    "\n",
    "OAuth 是一个关于授权（authorization）的开放网络标准。允许用户提供一个令牌，而不是用户名和密码来访问他们存放在特定服务提供者的数据。现在的版本是 2.0 版。  \n",
    "严格意义上来讲，OAuth2 不是一个标准协议，而是一个安全的授权框架。它详细描述了系统中不同角色、用户、服务前端应用（比如 API），以及客户端（比如网站或移动 App）之间怎么实现相互认证。 \n",
    "\n",
    "# JWT\n",
    "\n",
    "最后，重点介绍一下 JWT，JWT 是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器，服务器验证用户提交信息信息的合法性；如果验证成功，会产生并返回一个 Token（令牌），用户可以使用这个 token 访问服务器上受保护的资源。\n",
    "\n",
    "## JWT 特点:  \n",
    "1. 体积小，因而传输速度快  \n",
    "2. 传输方式多样，可以通过 URL/POST 参数 / HTTP 头部等方式传输  \n",
    "3. 严格的结构化。它自身（在 payload 中）就包含了所有与用户相关的验证消息，如用户可访问路由、访问有效期等信息，服务器无需再去连接数据库验证信息的有效性，并且 payload 支持为你的应用而定制化。  \n",
    "4. 支持跨域验证，可以应用于单点登录。\n",
    "\n",
    "JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案，编码之后的JWT看起来是这样的一串字符：\n",
    "\n",
    "```\n",
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ  \n",
    "```\n",
    "\n",
    "由 . 分为三段，通过解码可以得到：\n",
    "\n",
    "1. Header 头部分头部分简单声明了类型 (JWT) 以及产生签名所使用的算法。{“alg”:“AES256”,“typ”:“JWT”}\n",
    "\n",
    "2. playload(载荷) 中的 Claims 声明部分是整个 token 的核心，表示要发送的用户详细信息。有些情况下，我们很可能要在一个服务器上实现认证，然后访问另一台服务器上的资源；或者，通过单独的接口来生成 token，token 被保存在应用程序客户端（比如浏览器）使用。一个简单的声明（claim）的例子：{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}\n",
    "\n",
    "3. Signature 签名签名的目的是为了保证上边两部分信息不被篡改。如果尝试使用 Bas64 对解码后的 token 进行修改，签名信息就会失效。一般使用一个私钥（private key）通过特定算法对 Header 和 Claims 进行混淆产生签名信息，所以只有原始的 token 才能于签名信息匹配。这里有一个重要的实现细节。只有获取了私钥的应用程序（比如服务器端应用）才能完全认证 token 包含声明信息的合法性。所以，永远不要把私钥信息放在客户端（比如浏览器）。\n",
    "\n",
    "签名的目的：签名实际上是对头部以及载荷内容进行签名。所以，如果有人对头部以及载荷的内容解码之后进行修改，再进行编码的话，那么新的头部和载荷的签名和之前的签名就将是不一样的。而且，如果不知道服务器加密的时候用的密钥的话，得出来的签名也一定会是不一样的。  \n",
    "这样就能保证 token 不会被篡改。\n",
    "\n",
    "最后，我们将上面拼接完的字符串用 HS256 算法进行加密。在加密的时候，我们还需要提供一个密钥（secret）。类似盐\n",
    "\n",
    "这里在第三步我们得到 JWT 之后，需要将 JWT 存放在 client，之后的每次需要认证的请求都要把 JWT 发送过来。（请求时可以放到 header 的 Authorization ）\n",
    "\n",
    "## 在 web 框架 Django 中的具体应用:\n",
    "\n",
    "安装 pyjwt\n",
    "\n",
    "```sh\n",
    "pip3 install pyjwt\n",
    "\n",
    "```\n",
    "\n",
    "在用户登录成功后，生成一个 token\n",
    "\n",
    "```py\n",
    "import jwt\n",
    "encoded_jwt = jwt.encode({'username':'admin','site':'https://v3u.cn'},'secret_key',algorithm='HS256')\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "将这个 token 交给前端，以后前端访问任意接口都将在 header 里带着这个令牌 (token)，用来做认证，然后我们肯定不能每一个视图方法都做验证，所以可以利用装饰器做一个统一用户认证模块\n",
    "\n",
    "```py\n",
    "#定义验证装饰器\n",
    "from django.http import JsonResponse\n",
    "import jwt\n",
    "def auth_required():\n",
    "    def decorator(view_func):\n",
    "        def _wrapped_view(self,request, *args, **kwargs):\n",
    "\n",
    "\n",
    "            try:\n",
    "                auth = request.META.get('HTTP_AUTHORIZATION').split()\n",
    "            except AttributeError:\n",
    "                return HttpResponse('没权限')\n",
    "            \n",
    "            try:\n",
    "                dict = jwt.decode(auth[1], settings.SECRET_KEY, algorithms=['HS256'])\n",
    "                username = dict.get('data').get('username')\n",
    "            except jwt.ExpiredSignatureError:\n",
    "                return JsonResponse({\"status_code\": 401, \"message\": \"Token expired\"})\n",
    "            except jwt.InvalidTokenError:\n",
    "                return JsonResponse({\"status_code\": 401, \"message\": \"Invalid token\"})\n",
    "            except Exception as e:\n",
    "                return JsonResponse({\"status_code\": 401, \"message\": \"Can not get user object\"})\n",
    "            return view_func(request, *args, **kwargs)\n",
    "\n",
    "        return _wrapped_view\n",
    "\n",
    "    return decorator\n",
    "\n",
    "```\n",
    "\n",
    "至此，一个简单的 jwt 用户认证方法就写好了，至于 jwt 中的令牌存在客户端的什么位置呢？可以参考这一篇文章来寻找答案：[彻底弄清楚 session,cookie,sessionStorage,localStorage 的区别及应用场景（面试向）](https://v3u.cn/Index_a_id_94)\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
